/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Ola Bini <Ola.Bini@ki.se>
 * Copyright (C) 2006 Daniel Steer <damian.steer@hp.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.lang {

import asruby.Ruby;
import asruby.asupport.AsUtil;
import asruby.runtime.Block;
import asruby.runtime.CallbackFactory;
import asruby.runtime.ClassIndex;
import asruby.runtime.IRubyObject;

/**
 * The implementation of the built-in class Array in Ruby.
 *
 * Concurrency: no synchronization is required among readers, but
 * all users must synchronize externally with writers.
 *
 */
public class RubyArray extends RubyObject {
 	
    public static function createArrayClass(runtime:Ruby):RubyClass {
        var arrayc: RubyClass =  runtime.defineClass("Array", runtime.getObject(), ARRAY_ALLOCATOR);
        runtime.setArray(arrayc);
        arrayc.index = ClassIndex.ARRAY;
        arrayc.kindOf = function isKindOf(obj:IRubyObject, type:RubyModule):Boolean {
                return obj instanceof RubyArray;
            };
                    
        var callbackFactory: CallbackFactory =  runtime.callbackFactory(RubyArray);

        arrayc.includeModule(runtime.getEnumerable());
        arrayc.defineAnnotatedMethods(RubyArray);
        arrayc.dispatcher = callbackFactory.createDispatcher(arrayc);

        return arrayc;
    }

    private static var ARRAY_ALLOCATOR: Function =  
        function allocate(runtime:Ruby, klass:RubyClass):IRubyObject {
            return new RubyArray(runtime, klass);
        };
    
    public override function getNativeTypeIndex():int {
        return ClassIndex.ARRAY;
    }

    //rb_ary_s_create
    public static function create(klass:IRubyObject, args:Array, block:Block):IRubyObject {
        var arr: RubyArray =  RubyArray(RubyClass(klass).allocate());
        arr.callInit(RubyObject.NULL_ARRAY, block);
    
        if (args.length > 0) {
           // arr.alloc(args.length);
           // System.arraycopy(args, 0, arr.values, 0, args.length);
            arr.values = args.slice(0, args.length);
            arr.realLength = args.length;
        }
        return arr;
    }

    // rb_ary_new2
   
    public static function newArray(runtime:Ruby,  len:Number):RubyArray {
        return new RubyArray(runtime, null, len);
    }

    public static function newArray2(runtime:Ruby, obj:IRubyObject):RubyArray {
        return new RubyArray(runtime, null, ARRAY_DEFAULT_SIZE, [ obj ]);
    }

    // rb_assoc_new
    public static function newArray3(runtime:Ruby, car:IRubyObject, cdr:IRubyObject):RubyArray {
        return new RubyArray(runtime, null, ARRAY_DEFAULT_SIZE, [ car, cdr ]);
    }
    
    public static function newEmptyArray(runtime:Ruby):RubyArray {
        return new RubyArray(runtime, null, ARRAY_DEFAULT_SIZE, NULL_ARRAY);
    }

    // rb_ary_new4, rb_ary_new3
    public static function newArray4(runtime:Ruby, args:Array):RubyArray {
        var arr: RubyArray =  new RubyArray(runtime, null, args.length, null);
        arr.values = args.slice(0, args.length);
        //System.arraycopy(args, 0, arr.values, 0, args.length);
        arr.realLength = args.length;
        return arr;
    }
   
    public static function newArrayNoCopy(runtime:Ruby, args:Array):RubyArray {
        return new RubyArray(runtime, null, ARRAY_DEFAULT_SIZE, args);
    }
/*    
    public static function newArrayNoCopy2(runtime:Ruby, args:Array, begin:int):RubyArray {
        return new RubyArray(runtime, args, begin);
    }
    
    public static function newArrayNoCopyLight(runtime:Ruby, args:Array):RubyArray {
        var arr: RubyArray =  new RubyArray(runtime, false);
        arr.values = args;
        arr.realLength = args.length;
        return arr;
    }

    public static function newArray5(runtime:Ruby, collection:Array):RubyArray {
        var arr: RubyArray =  new RubyArray(runtime, collection.size());
        collection.toArray(arr.values);
        arr.realLength = arr.values.length;
        return arr;
    }
*/
    public static const ARRAY_DEFAULT_SIZE: int =  16;    

    private  var values:Array;

    private static var TMPLOCK_ARR_F: int =  1 << 9;
    private static var TMPLOCK_OR_FROZEN_ARR_F: int =  TMPLOCK_ARR_F | FROZEN_F;

    private var isShared:Boolean = false;
    private  var begin: int =  0;
    private  var realLength: int =  0;

/*
    private  function RubyArray(runtime:Ruby, vals:Array) {
        super(runtime, runtime.getArray());
        values = vals;
        realLength = vals.length;
    }

    private  function RubyArray(runtime:Ruby, vals:Array, begin:int) {
        super(runtime, runtime.getArray());
        this.values = vals;
        this.begin = begin;
        this.realLength = vals.length - begin;
        this.isShared = true;
    }
    
    private  function RubyArray(runtime:Ruby, length:long) {
        super(runtime, runtime.getArray());
        checkLength(length);
        alloc(length);
    }

    private  function RubyArray(runtime:Ruby, length:long, objectspace:Boolean) {
        super(runtime, runtime.getArray(), objectspace);
        checkLength(length);
        alloc(length);
    }   
*/
    // rb_ary_new3, rb_ary_new4
    // allocates the internal array of size length and copies the 'length' elements

    public  function RubyArray(runtime:Ruby, klass:RubyClass=null, length:Number=ARRAY_DEFAULT_SIZE, vals:Array=null) {
        super(runtime, runtime.getArray());
        checkLength(length);
        if ( vals != null ) {
	        var ilength: int =  length;
	        alloc(ilength);
	        if (ilength > 0 && vals.length > 0) values = vals.slice(0, ilength);
	
	        realLength = ilength;
        }
    }
/*
    // NEWOBJ and OBJSETUP equivalent
    // fastest one, for shared arrays, optional objectspace
    private  function RubyArray(runtime:Ruby, objectSpace:Boolean) {
        super(runtime, runtime.getArray(), objectSpace);
    }

    private  function RubyArray(runtime:Ruby) {
        super(runtime, runtime.getArray());
        alloc(ARRAY_DEFAULT_SIZE);
    }

    public  function RubyArray(runtime:Ruby, klass:RubyClass) {
        super(runtime, klass);
        //alloc(ARRAY_DEFAULT_SIZE);
    }
    
    private  function RubyArray(runtime:Ruby, klass:RubyClass, length:int=-1) {
        super(runtime, klass);
        alloc(length);
    }


    function RubyArray(runtime:Ruby, klass:RubyClass=null, length:Number=0) {
    	if ( klass == null ) {
    		klass = runtime.getArray();
    	}
        super(runtime, klass);
        checkLength(length);
        alloc(length);
    }

     private  function RubyArray(runtime:Ruby, klass:RubyClass, length:Number, objectspace:Boolean) {
        super(runtime, klass, objectspace);
        checkLength(length);
        alloc(length);
    }    

    private  function RubyArray(runtime:Ruby, klass:RubyClass, objectSpace:Boolean) {
        super(runtime, klass, objectSpace);
    }
    
    private  function RubyArray(runtime:Ruby, klass:RubyClass, original:RubyArray) {
        super(runtime, klass);
        realLength = original.realLength;
        alloc(realLength);
        System.arraycopy(original.values, original.begin, values, 0, realLength);
    }
     */
    private  function reserve(length:int):Array {
        return new IRubyObject[length];
    }

    private  function alloc(length:int):void {
        values = new Array;
    }

    private  function realloc(newLength:int):void {
        var reallocated: Array =  new Array;
        //System.arraycopy(values, 0, reallocated, 0, newLength > realLength ? realLength : newLength);
        values = reallocated;
    }

    private  function checkLength(length:Number):void {
        if (length < 0) {
            throw getRuntime().newArgumentError("negative array size (or size too big)");
        }

        if (length >= Number.MAX_VALUE) {
            throw getRuntime().newArgumentError("array size too big");
        }
    }

    // Getter for property list.
    //@return Value of property list.
    /*
    public  function getList():Array {
        return toJavaArray(); 
    }*/

    public  function getLength():int {
        return realLength;
    }
/*
    public  function toJavaArray():Array {
        var copy: Array =  reserve(realLength);
        System.arraycopy(values, begin, copy, 0, realLength);
        return copy;
    }
    
    public  function toJavaArrayUnsafe():Array {
        return !isShared ? values : toJavaArray();
    }    

    public  function toJavaArrayMaybeUnsafe():Array {
        return (!isShared && begin == 0 && values.length == realLength) ? values : toJavaArray();
    }    

    // rb_ary_make_shared
    private  function makeShared(beg:int, len:int, klass:RubyClass):RubyArray {
        return makeShared(beg, len, klass, klass.getRuntime().isObjectSpaceEnabled());
    }    
    
    // rb_ary_make_shared
    private  function makeShared2(beg:int, len:int, klass:RubyClass, objectSpace:Boolean):RubyArray {
        var sharedArray: RubyArray =  new RubyArray(getRuntime(), klass, objectSpace);
        isShared = true;
        sharedArray.values = values;
        sharedArray.isShared = true;
        sharedArray.begin = beg;
        sharedArray.realLength = len;
        return sharedArray;
    }

    // rb_ary_modify_check
    private  function modifyCheck():void {
        if ((flags & TMPLOCK_OR_FROZEN_ARR_F) != 0) {
            if ((flags & FROZEN_F) != 0) throw getRuntime().newFrozenError("array");           
            if ((flags & TMPLOCK_ARR_F) != 0) throw getRuntime().newTypeError("can't modify array during iteration");
        }
        if (!isTaint() && getRuntime().getSafeLevel() >= 4) {
            throw getRuntime().newSecurityError("Insecure: can't modify array");
        }
    }

    //rb_ary_modify
    private  function modify():void {
        modifyCheck();
        if (isShared) {
            var vals: Array =  reserve(realLength);
            isShared = false;
            System.arraycopy(values, begin, vals, 0, realLength);
            begin = 0;            
            values = vals;
        }
    }


    //rb_ary_initialize
    public override function initialize(context:ThreadContext, args:Array, block:Block):IRubyObject {
        var argc: int =  args.length;
        var runtime: Ruby =  getRuntime();

        if (argc == 0) {
            modifyCheck();
            realLength = 0;
            if (block.isGiven()) runtime.getWarnings().warn(RubyWarnings.BLOCK_UNUSED, "given block not used");

    	    return this;
    	}

        if (argc == 1 && !(args[0] instanceof RubyFixnum)) {
            var val: IRubyObject =  args[0].checkArrayType();
            if (!val.isNil()) {
                replace(val);
                return this;
            }
        }

        var len: Number =  RubyNumeric.num2long(args[0]);

        if (len < 0) throw runtime.newArgumentError("negative array size");

        if (len >= Number.MAX_VALUE) throw runtime.newArgumentError("array size too big");

        var ilen: int =   len;

        modify();

        if (ilen > values.length) values = reserve(ilen);

        if (block.isGiven()) {
            if (argc == 2) {
                runtime.getWarnings().warn(RubyWarnings.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");
            }

            for (var i:int = 0; i < ilen; i++) {
                store(i, block.yield(context, new RubyFixnum(runtime, i)));
                realLength = i + 1;
            }
        } else {
            Arrays.fill(values, 0, ilen, (argc == 2) ? args[1] : runtime.getNil());
            realLength = ilen;
        }
    	return this;
    }

    //rb_ary_initialize_copy
    public override function initialize_copy(orig:IRubyObject):IRubyObject {
        return this.replace(orig);
    }
    
    //rb_ary_replace
    public  function replace(orig:IRubyObject):IRubyObject {
        modifyCheck();

        var origArr: RubyArray =  orig.convertToArray();

        if (this == orig) return this;

        origArr.isShared = true;
        isShared = true;
        values = origArr.values;
        realLength = origArr.realLength;
        begin = origArr.begin;


        return this;
    }

    //rb_ary_to_s
    public override function to_s():IRubyObject {
        if (realLength == 0) return getRuntime().newString("");

        return join(getRuntime().getCurrentContext(), getRuntime().getGlobalVariables().get("$,"));
    }

    
    public  function includes(context:ThreadContext, item:IRubyObject):Boolean {
        var begin: int =  this.begin;
        
        for (var i:int = begin; i < begin + realLength; i++) {
            if (equalInternal(context, values[i], item).isTrue()) return true;
    	}
        
        return false;
    }

    //rb_ary_hash
    public override function hash(context:ThreadContext):RubyFixnum {
        var h: int =  realLength;

        var runtime: Ruby =  getRuntime();
        var begin: int =  this.begin;
        for (var i:int = begin; i < begin + realLength; i++) {
            h = (h << 1) | (h < 0 ? 1 : 0);
            h ^= RubyNumeric.num2long(values[i].callMethod(context, MethodIndex.HASH, "hash"));
        }

        return runtime.newFixnum(h);
    }

    //rb_ary_store
    public  function store(index:Number, value:IRubyObject):IRubyObject {
        if (index < 0) {
            index += realLength;
            if (index < 0) {
                throw getRuntime().newIndexError("index " + (index - realLength) + " out of array");
            }
        }

        modify();

        if (index >= realLength) {
            if (index >= values.length) {
                var newLength: Number =  values.length >> 1;

                if (newLength < ARRAY_DEFAULT_SIZE) newLength = ARRAY_DEFAULT_SIZE;

                newLength += index;
                if (newLength >= Number.MAX_VALUE) {
                    throw getRuntime().newArgumentError("index too big");
                }
                realloc( newLength);
            }
            if(index != realLength) Arrays.fill(values, realLength,  index + 1, getRuntime().getNil());
            
            realLength =  index + 1;
        }

        values[ index] = value;
        return value;
    }

    //rb_ary_elt - faster
    private  function elt(offset:Number):IRubyObject {
        if (realLength == 0 || offset < 0 || offset >= realLength) return getRuntime().getNil();

        return values[begin +  offset];
    }

    //rb_ary_elt - faster
    private  function elt2(offset:int):IRubyObject {
        if (realLength == 0 || offset < 0 || offset >= realLength) return getRuntime().getNil();

        return values[begin + offset];
    }

    //rb_ary_elt - faster
    private  function elt_f(offset:Number):IRubyObject {
        if (realLength == 0 || offset >= realLength) return getRuntime().getNil();

        return values[begin +  offset];
    }

    //rb_ary_elt - faster
    private  function elt_f2(offset:int):IRubyObject {
        if (realLength == 0 || offset >= realLength) return getRuntime().getNil();

        return values[begin + offset];
    }

    //rb_ary_entry
    public  function entry(offset:Number):IRubyObject {
        return (offset < 0 ) ? elt(offset + realLength) : elt_f(offset);
    }


    //rb_ary_entry
    public  function entry2(offset:int):IRubyObject {
        return (offset < 0 ) ? elt(offset + realLength) : elt_f(offset);
    }
*/
    public  function eltInternal(offset:int):IRubyObject {
        return values[begin + offset];
    }
/*  
    public  function eltInternalSet(offset:int, item:IRubyObject):IRubyObject {
        return values[begin + offset] = item;
    }
    
    //rb_ary_fetch
    public  function fetch(context:ThreadContext, args:Array, block:Block):IRubyObject {
        if (args.length == 2 && block.isGiven()) {
            getRuntime().getWarnings().warn(RubyWarnings.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");
        }

        var index: Number =  RubyNumeric.num2long(args[0]);

        if (index < 0) index += realLength;

        if (index < 0 || index >= realLength) {
            if (block.isGiven()) return block.yield(context, args[0]);

            if (args.length == 1) {
                throw getRuntime().newIndexError("index " + index + " out of array");
            }
            
            return args[1];
        }
        
        return values[begin +  index];
    }

    //rb_ary_to_ary
    private static function aryToAry(obj:IRubyObject):RubyArray {
        if (obj instanceof RubyArray) return RubyArray(obj);

        if (obj.respondsTo("to_ary")) return obj.convertToArray();

        var arr: RubyArray =  new RubyArray(obj.getRuntime(), false); // possibly should not in object space
        arr.alloc(1);
        arr.values[0] = obj;
        arr.realLength = 1;
        return arr;
    }

    //rb_ary_splice
    private  function splice(beg:Number, len:Number, rpl:IRubyObject):void {
        var rlen:int;

        if (len < 0) throw getRuntime().newIndexError("negative length (" + len + ")");

        if (beg < 0) {
            beg += realLength;
            if (beg < 0) {
                beg -= realLength;
                throw getRuntime().newIndexError("index " + beg + " out of array");
            }
        }
        
        if (beg + len > realLength) len = realLength - beg;

        var rplArr:RubyArray;
        if (rpl == null || rpl.isNil()) {
            rplArr = null;
            rlen = 0;
        } else {
            rplArr = aryToAry(rpl);
            rlen = rplArr.realLength;
        }

        modify();

        if (beg >= realLength) {
            len = beg + rlen;

            if (len >= values.length) {
                var tryNewLength: int =  values.length + (values.length >> 1);
                
                realloc(len > tryNewLength ? len : tryNewLength);
            }

            Arrays.fill(values, realLength,  beg, getRuntime().getNil());
            if (rlen > 0) {
                System.arraycopy(rplArr.values, rplArr.begin, values,  beg, rlen);
            }
            realLength =  len;
        } else {
            var alen:Number;

            if (beg + len > realLength) len = realLength - beg;

            alen = realLength + rlen - len;
            if (alen >= values.length) {
                var tryNewLength: int =  values.length + (values.length >> 1);
                
                realloc(alen > tryNewLength ? alen : tryNewLength);
            }

            if (len != rlen) {
                System.arraycopy(values,  (beg + len), values,  beg + rlen, realLength -  (beg + len));
                realLength =  alen;
            }

            if (rlen > 0) {
                System.arraycopy(rplArr.values, rplArr.begin, values,  beg, rlen);
            }
        }
    }

    //rb_ary_insert
    public  function insert(args:Array):IRubyObject {
        if (args.length == 1) return this;

        if (args.length < 1) {
            throw getRuntime().newArgumentError("wrong number of arguments (at least 1)");
        }

        var pos: Number =  RubyNumeric.num2long(args[0]);

        if (pos == -1) pos = realLength;
        if (pos < 0) pos++;

        var inserted: RubyArray =  new RubyArray(getRuntime(), false);
        inserted.values = args;
        inserted.begin = 1;
        inserted.realLength = args.length - 1;
        
        splice(pos, 0, inserted); // rb_ary_new4
        
        return this;
    }

    //rb_ary_dup
    public  function aryDup():RubyArray {
        var dup: RubyArray =  new RubyArray(getRuntime(), getMetaClass(), this);
        dup.flags |= flags & TAINTED_F; // from DUP_SETUP
        // rb_copy_generic_ivar from DUP_SETUP here ...unlikely..
        return dup;
    }

    //rb_ary_transpose
    public  function transpose():RubyArray {
        var tmp, result:RubyArray = null;

        var alen: int =  realLength;
        if (alen == 0) return aryDup();
    
        var runtime: Ruby =  getRuntime();
        var elen: int =  -1;
        var end: int =  begin + alen;
        for (var i:int = begin; i < end; i++) {
            tmp = elt(i).convertToArray();
            if (elen < 0) {
                elen = tmp.realLength;
                result = new RubyArray(runtime, elen);
                for (var j:int = 0; j < elen; j++) {
                    result.store(j, new RubyArray(runtime, alen));
                }
            } else if (elen != tmp.realLength) {
                throw runtime.newIndexError("element size differs (" + tmp.realLength
                        + " should be " + elen + ")");
            }
            for (var j:int = 0; j < elen; j++) {
                (result.elt(j)).store(i - begin, tmp.elt(j));
            }
        }
        return result;
    }

    //rb_values_at (internal)
    private  function values_at(olen:Number, args:Array):IRubyObject {
        var result: RubyArray =  new RubyArray(getRuntime(), args.length);

        for (var i:int = 0; i < args.length; i++) {
            if (args[i] instanceof RubyFixnum) {
                result.append(entry((args[i]).getLongValue()));
                continue;
            }

            var beglen:Array;
            if (!(args[i] instanceof RubyRange)) {
            } else if ((beglen = (args[i]).begLen(olen, 0)) == null) {
                continue;
            } else {
                var beg: int =   beglen[0];
                var len: int =   beglen[1];
                var end: int =  begin + len;
                for (var j:int = begin; j < end; j++) {
                    result.append(entry(j + beg));
                }
                continue;
            }
            result.append(entry(RubyNumeric.num2long(args[i])));
        }

        return result;
    }

    //rb_values_at
    public function values_at2(args:Array):IRubyObject {
        return super.values_at(realLength, args);
    }

    //rb_ary_subseq
    public  function subseq(beg:Number, len:Number):IRubyObject {
        if (beg > realLength || beg < 0 || len < 0) return getRuntime().getNil();

        if (beg + len > realLength) {
            len = realLength - beg;
            
            if (len < 0) len = 0;
        }
        
        if (len == 0) return new RubyArray(getRuntime(), getMetaClass(), 0);

        return makeShared(begin +  beg,  len, getMetaClass());
    }

    //rb_ary_subseq
    public  function subseqLight(beg:Number, len:Number):IRubyObject {
        if (beg > realLength || beg < 0 || len < 0) return getRuntime().getNil();

        if (beg + len > realLength) {
            len = realLength - beg;
            
            if (len < 0) len = 0;
        }
        
        if (len == 0) return new RubyArray(getRuntime(), getMetaClass(), 0, false);

        return makeShared(begin +  beg,  len, getMetaClass(), false);
    }

    //rb_ary_length
    public  function length():RubyFixnum {
        return getRuntime().newFixnum(realLength);
    }
*/
    //rb_ary_push - specialized rb_ary_store 
    public  function append(item:IRubyObject):RubyArray {
        //modify();
        
        if (realLength == values.length) {
        if (realLength == Number.MAX_VALUE) throw getRuntime().newArgumentError("index too big");
            
            var newLength: Number =  values.length + (values.length >> 1);
            if ( newLength > Number.MAX_VALUE ) {
                newLength = Number.MAX_VALUE;
            }else if ( newLength < ARRAY_DEFAULT_SIZE ) {
                newLength = ARRAY_DEFAULT_SIZE;
            }

            //realloc( newLength);
        }
        
        values[realLength++] = item;
        return this;
    }
/*
    //rb_ary_push_m
    // FIXME: Whis is this named "push_m"?
    public  function push_m(items:Array):RubyArray {
        for (var i:int = 0; i < items.length; i++) {
            append(items[i]);
        }
        
        return this;
    }

    //rb_ary_pop
    public  function pop():IRubyObject {
        modifyCheck();
        
        if (realLength == 0) return getRuntime().getNil();

        if (!isShared) {
            var index: int =  begin + --realLength;
            var obj: IRubyObject =  values[index];
            values[index] = null;
            return obj;
        } 

        return values[begin + --realLength];
    }

    //rb_ary_shift
    public  function shift():IRubyObject {
        modify();

        if (realLength == 0) return getRuntime().getNil();

        var obj: IRubyObject =  values[begin];
        values[begin] = null;

        isShared = true;

        begin++;
        realLength--;

        return obj;
    }

    //rb_ary_unshift
    public  function unshift(item:IRubyObject):RubyArray {
        modify();

        if (realLength == values.length) {
            var newLength: int =  values.length >> 1;
            if (newLength < ARRAY_DEFAULT_SIZE) newLength = ARRAY_DEFAULT_SIZE;

            newLength += values.length;
            realloc(newLength);
        }
        System.arraycopy(values, 0, values, 1, realLength);

        realLength++;
        values[0] = item;

        return this;
    }

    //rb_ary_unshift_m
    public  function unshift_m(items:Array):RubyArray {
        var len: Number =  realLength;

        if (items.length == 0) return this;

        store(len + items.length - 1, getRuntime().getNil());

        // it's safe to use zeroes here since modified by store()
        System.arraycopy(values, 0, values, items.length,  len);
        System.arraycopy(items, 0, values, 0, items.length);
        
        return this;
    }

    //rb_ary_includes
    public  function include_p(context:ThreadContext, item:IRubyObject):RubyBoolean {
        return getRuntime().newBoolean(includes(context, item));
    }

    //rb_ary_frozen_p
    public  function frozen_p():RubyBoolean {
        return getRuntime().newBoolean(isFrozen() || (flags & TMPLOCK_ARR_F) != 0);
    }

    //rb_ary_aref
    public  function aref(args:Array):IRubyObject {
        var beg, len:Number;

        if(args.length == 1) {
            if (args[0] instanceof RubyFixnum) return entry((args[0]).getLongValue());
            if (args[0] instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");
            
            var beglen:Array;
            if (!(args[0] instanceof RubyRange)) {
            } else if ((beglen = ( args[0]).begLen(realLength, 0)) == null) {
                return getRuntime().getNil();
            } else {
                beg = beglen[0];
                len = beglen[1];
                return subseq(beg, len);
            }

            return entry(RubyNumeric.num2long(args[0]));            
        }        

        if (args.length == 2) {
            if (args[0] instanceof RubySymbol) {
                throw getRuntime().newTypeError("Symbol as array index");
            }
            beg = RubyNumeric.num2long(args[0]);
            len = RubyNumeric.num2long(args[1]);

            if (beg < 0) beg += realLength;

            return subseq(beg, len);
        }
        
        return null;
    }

    //rb_ary_aset
    public  function aset(args:Array):IRubyObject {
        if (args.length == 2) {
        if (args[0] instanceof RubyFixnum) {
                store((args[0]).getLongValue(), args[1]);
            return args[1];
        }
        if (args[0] instanceof RubyRange) {
            var beglen: Array =  (args[0]).begLen(realLength, 1);
            splice(beglen[0], beglen[1], args[1]);
            return args[1];
        }
            if (args[0] instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");

        store(RubyNumeric.num2long(args[0]), args[1]);
        return args[1];
    }

        if (args.length == 3) {
            if (args[0] instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");
            if (args[1] instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as subarray length");

            splice(RubyNumeric.num2long(args[0]), RubyNumeric.num2long(args[1]), args[2]);
            return args[2];
        }

        throw getRuntime().newArgumentError("wrong number of arguments (" + args.length + " for 2)");
    }

    //rb_ary_at
    public  function at(pos:IRubyObject):IRubyObject {
        return entry(RubyNumeric.num2long(pos));
    }

	// rb_ary_concat
    public  function concat(obj:IRubyObject):RubyArray {
        var ary: RubyArray =  obj.convertToArray();
        
        if (ary.realLength > 0) splice(realLength, 0, ary);

        return this;
    }

    //inspect_ary
    private  function inspectAry(context:ThreadContext):IRubyObject {
        var buffer: StringBuffer =  new StringBuffer("[");
        var runtime: Ruby =  getRuntime();
        var tainted: Boolean =  isTaint();

        for (var i:int = 0; i < realLength; i++) {
            var s: RubyString =  RubyString.objAsString(context, values[begin + i].callMethod(context, "inspect"));

            if (s.isTaint()) tainted = true;

            if (i > 0) buffer.append(", ");

            buffer.append(s.toString());
        }
        buffer.append("]");

        var str: RubyString =  runtime.newString(buffer.toString());
        if (tainted) str.setTaint(true);

        return str;
    }

    // rb_ary_inspect
    public  function inspect():IRubyObject {
        if (realLength == 0) return getRuntime().newString("[]");
        if (getRuntime().isInspecting(this)) return  getRuntime().newString("[...]");

        try {
            function getRuntime().registerInspecting(this);
            return inspectAry(getRuntime().getCurrentContext());
        } ly {
            function getRuntime().unregisterInspecting(this);
        }
    } 

    // rb_ary_first
    public  function first(args:Array):IRubyObject {
    	if (args.length == 0) {
            if (realLength == 0) return getRuntime().getNil();

            return values[begin];
        } 
            
        var n: Number =  RubyNumeric.num2long(args[0]);
        if (n > realLength) {
            n = realLength;
        } else if (n < 0) {
            throw getRuntime().newArgumentError("negative array size (or size too big)");
    	}
    	
        return makeShared(begin,  n, getRuntime().getArray());
    }

    // rb_ary_last
    public  function last(args:Array):IRubyObject {
        if (args.length == 0) {
            if (realLength == 0) return getRuntime().getNil();

            return values[begin + realLength - 1];
        } 
            
        var n: Number =  RubyNumeric.num2long(args[0]);
        if (n > realLength) {
            n = realLength;
        } else if (n < 0) {
            throw getRuntime().newArgumentError("negative array size (or size too big)");
        }

        return makeShared(begin + realLength -  n,  n, getRuntime().getArray());
    }

    // rb_ary_each
    public  function each(context:ThreadContext, block:Block):IRubyObject {
        for (var i:int = 0; i < realLength; i++) {
            block.yield(context, values[begin + i]);
        }
        return this;
    }

    // rb_ary_each_index
    public  function each_index(context:ThreadContext, block:Block):IRubyObject {
        var runtime: Ruby =  getRuntime();
        for (var i:int = 0; i < realLength; i++) {
            block.yield(context, runtime.newFixnum(i));
        }
        return this;
    }

    // rb_ary_reverse_each
    public  function reverse_each(context:ThreadContext, block:Block):IRubyObject {
        var len: int =  realLength;
        
        while(len-- > 0) {
            block.yield(context, values[begin + len]);
            
            if (realLength < len) len = realLength;
        }
        
        return this;
    }

    private  function inspectJoin(context:ThreadContext, tmp:RubyArray, sep:IRubyObject):IRubyObject {
        var runtime: Ruby =  getRuntime();

        // If already inspecting, there is no need to register/unregister again.
        if (runtime.isInspecting(this)) {
            return tmp.join(context, sep);
        }

        try {
            runtime.registerInspecting(this);
            return tmp.join(context, sep);
        } ly {
            runtime.unregisterInspecting(this);
        }
    } 

    //rb_ary_join
    public  function join(context:ThreadContext, sep:IRubyObject):RubyString {
        if (realLength == 0) return getRuntime().newString("");

        var taint: Boolean =  isTaint() || sep.isTaint();

        var len: Number =  1;
        for (var i:int = begin; i < begin + realLength; i++) {            
            var tmp: IRubyObject =  values[i].checkStringType();
            len += tmp.isNil() ? 10 : ( tmp).getByteList().length();
        }

        var strSep: RubyString =  null;
        if (!sep.isNil()) {
            sep = strSep = sep.convertToString();
            len += strSep.length * (realLength - 1);
        }

        var buf: String =  new String(len);
        var runtime: Ruby =  getRuntime();
        for (var i:int = begin; i < begin + realLength; i++) {
            var tmp: IRubyObject =  values[i];
            if (tmp instanceof RubyString) {
                // do nothing
            } else if (tmp instanceof RubyArray) {
                if (runtime.isInspecting(tmp)) {
                    tmp = runtime.newString("[...]");
                } else {
                    tmp = inspectJoin(context, tmp, sep);
                }
            } else {
                tmp = RubyString.objAsString(context, tmp);
            }

            if (i > begin && !sep.isNil()) buf.append(strSep.getByteList());

            buf.append(tmp.asString().getByteList());
            if (tmp.isTaint()) taint = true;
        }

        var result: RubyString =  runtime.newString(buf); 

        if (taint) result.setTaint(true);

        return result;
    }

    //rb_ary_join_m
    public  function join_m(context:ThreadContext, args:Array):RubyString {
        var argc: int =  args.length;
        var sep: IRubyObject =  (argc == 1) ? args[0] : getRuntime().getGlobalVariables().get("$,");
        
        return join(context, sep);
    }

    //rb_ary_to_a
    public override function to_a():RubyArray {
        if(getMetaClass() != getRuntime().getArray()) {
            var dup: RubyArray =  new RubyArray(getRuntime(), getRuntime().isObjectSpaceEnabled());

            isShared = true;
            dup.isShared = true;
            dup.values = values;
            dup.realLength = realLength; 
            dup.begin = begin;
            
            return dup;
        }        
        return this;
    }

    public  function to_ary():IRubyObject {
    	return this;
    }

    public override function convertToArray():RubyArray {
        return this;
    }
   
    public override function checkArrayType():IRubyObject{
        return this;
    }

    //rb_ary_equal
    public override function op_equal(context:ThreadContext, obj:IRubyObject):IRubyObject {
        if (this == obj) return getRuntime().getTrue();

        if (!(obj instanceof RubyArray)) {
            if (!obj.respondsTo("to_ary")) {
                return getRuntime().getFalse();
            } else {
                return equalInternal(context, obj.callMethod(context, "to_ary"), this);
            }
        }

        var ary: RubyArray =   RubyArray(obj);
        if (realLength != ary.realLength) return getRuntime().getFalse();

        var runtime: Ruby =  getRuntime();
        for (var i:int = 0; i < realLength; i++) {
            if (!equalInternal(context, elt(i), ary.elt(i)).isTrue()) return runtime.getFalse();            
        }
        return runtime.getTrue();
    }

    //rb_ary_eql
    public override function eql_p(context:ThreadContext, obj:IRubyObject):RubyBoolean {
        if (this == obj) return getRuntime().getTrue();
        if (!(obj instanceof RubyArray)) return getRuntime().getFalse();

        var ary: RubyArray =   RubyArray(obj);

        if (realLength != ary.realLength) return getRuntime().getFalse();

        var runtime: Ruby =  getRuntime();
        for (var i:int = 0; i < realLength; i++) {
            if (!eqlInternal(context, elt(i), ary.elt(i))) return runtime.getFalse();
        }
        return runtime.getTrue();
    }

    //rb_ary_compact_bang
    public  function compact_bang():IRubyObject {
        modify();

        var p: int =  0;
        var t: int =  0;
        var end: int =  p + realLength;

        while (t < end) {
            if (values[t].isNil()) {
                t++;
            } else {
                values[p++] = values[t++];
            }
        }

        if (realLength == p) return getRuntime().getNil();

        realloc(p);
        realLength = p;
        return this;
    }

    //rb_ary_compact
    public  function compact():IRubyObject {
        var ary: RubyArray =  aryDup();
        ary.compact_bang();
        return ary;
    }

    //rb_ary_empty_p
    public  function empty_p():IRubyObject {
        return realLength == 0 ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    //rb_ary_clear
    public  function rb_clear():IRubyObject {
        modifyCheck();

        if(isShared) {
            alloc(ARRAY_DEFAULT_SIZE);
            isShared = true;
        } else if (values.length > ARRAY_DEFAULT_SIZE << 1){
            alloc(ARRAY_DEFAULT_SIZE << 1);
        }

        begin = 0;
        realLength = 0;
        return this;
    }

    //rb_ary_fill
    public  function fill(context:ThreadContext, args:Array, block:Block):IRubyObject {
        var item: IRubyObject =  null;
        var begObj: IRubyObject =  null;
        var lenObj: IRubyObject =  null;
        var argc: int =  args.length;

        if (block.isGiven()) {
            Arity.checkArgumentCount(getRuntime(), args, 0, 2);
            item = null;
        	begObj = argc > 0 ? args[0] : null;
        	lenObj = argc > 1 ? args[1] : null;
        	argc++;
        } else {
            Arity.checkArgumentCount(getRuntime(), args, 1, 3);
            item = args[0];
        	begObj = argc > 1 ? args[1] : null;
        	lenObj = argc > 2 ? args[2] : null;
        }

        var beg: int =  0, end = 0, len = 0;
        switch (argc) {
        case 1:
            beg = 0;
            len = realLength;
            break;
        case 2:
            if (begObj instanceof RubyRange) {
                var beglen: Array =  (begObj).begLen(realLength, 1);
                beg =  beglen[0];
                len =  beglen[1];
                break;
            }
            // fall through 
        case 3:
            beg = begObj.isNil() ? 0 : RubyNumeric.num2int(begObj);
            if (beg < 0) {
                beg = realLength + beg;
                if (beg < 0) beg = 0;
            }
            len = (lenObj == null || lenObj.isNil()) ? realLength - beg : RubyNumeric.num2int(lenObj);
            // TODO: In MRI 1.9, an explicit check for negative length is
            // added here. IndexError is raised when length is negative.
            // See [ruby-core:12953] for more details.
            break;
        }

        modify();

        end = beg + len;
        // apparently, that's how MRI does overflow check
        if (end < 0) {
            throw getRuntime().newArgumentError("argument too big");
        }
        if (end > realLength) {
            if (end >= values.length) realloc(end);

            Arrays.fill(values, realLength, end, getRuntime().getNil());
            realLength = end;
        }

        if (block.isGiven()) {
            var runtime: Ruby =  getRuntime();
            for (var i:int = beg; i < end; i++) {
                var v: IRubyObject =  block.yield(context, runtime.newFixnum(i));
                if (i >= realLength) break;

                values[i] = v;
            }
        } else {
            if(len > 0) Arrays.fill(values, beg, beg + len, item);
        }
        
        return this;
    }

    //rb_ary_index
    public  function index(context:ThreadContext, obj:IRubyObject):IRubyObject {
        var runtime: Ruby =  getRuntime();
        for (var i:int = begin; i < begin + realLength; i++) {
            if (equalInternal(context, values[i], obj).isTrue()) return runtime.newFixnum(i - begin);            
        }

        return runtime.getNil();
    }

    //rb_ary_rindex
    public  function rindex(context:ThreadContext, obj:IRubyObject):IRubyObject {
        var runtime: Ruby =  getRuntime();
        var i: int =  realLength;

        while (i-- > 0) {
            if (i > realLength) {
                i = realLength;
                continue;
            }
            if (equalInternal(context, values[begin + i], obj).isTrue()) return getRuntime().newFixnum(i);
        }

        return runtime.getNil();
    }

    //rb_ary_indexes
    public  function indexes(args:Array):IRubyObject {
        getRuntime().getWarnings().warn(RubyWarnings.DEPRECATED_METHOD, "Array#indexes is deprecated; use Array#values_at", "Array#indexes", "Array#values_at");

        var ary: RubyArray =  new RubyArray(getRuntime(), args.length);

        var arefArgs: Array =  new IRubyObject[1];
        for (var i:int = 0; i < args.length; i++) {
            arefArgs[0] = args[i];
            ary.append(aref(arefArgs));
        }

        return ary;
    }

    //rb_ary_reverse_bang
    public  function reverse_bang():IRubyObject {
        modify();

        var tmp:IRubyObject;
        if (realLength > 1) {
            var p1: int =  0;
            var p2: int =  p1 + realLength - 1;

            while (p1 < p2) {
                tmp = values[p1];
                values[p1++] = values[p2];
                values[p2--] = tmp;
            }
        }
        return this;
    }

    //rb_ary_reverse_m
    public  function reverse():IRubyObject {
        return aryDup().reverse_bang();
    }

    //rb_ary_collect
    public  function collect(context:ThreadContext, block:Block):RubyArray {
        var runtime: Ruby =  getRuntime();
        
        if (!block.isGiven()) return new RubyArray(getRuntime(), runtime.getArray(), this);
        
        var collect: RubyArray =  new RubyArray(runtime, realLength);
        
        for (var i:int = begin; i < begin + realLength; i++) {
            collect.append(block.yield(context, values[i]));
        }
        
        return collect;
    }

    //rb_ary_collect_bang
    public  function collect_bang(context:ThreadContext, block:Block):RubyArray {
        modify();
        for (var i:int = 0, len = realLength; i < len; i++) {
            store(i, block.yield(context, values[begin + i]));
        }
        return this;
    }

    //rb_ary_select
    public  function select(context:ThreadContext, block:Block):RubyArray {
        var runtime: Ruby =  getRuntime();
        var result: RubyArray =  new RubyArray(runtime, realLength);

        if (isShared) {
            for (var i:int = begin; i < begin + realLength; i++) {
                if (block.yield(context, values[i]).isTrue()) result.append(elt(i - begin));
            }
        } else {
            for (var i:int = 0; i < realLength; i++) {
                if (block.yield(context, values[i]).isTrue()) result.append(elt(i));
            }
        }
        return result;
    }

    //rb_ary_delete
    public  function delete0(context:ThreadContext, item:IRubyObject, block:Block):IRubyObject {
        var i2: int =  0;

        var runtime: Ruby =  getRuntime();
        for (var i1:int = 0; i1 < realLength; i1++) {
            var e: IRubyObject =  values[begin + i1];
            if (equalInternal(context, e, item).isTrue()) continue;
            if (i1 != i2) store(i2, e);
            i2++;
        }
        
        if (realLength == i2) {
            if (block.isGiven()) return block.yield(context, item);

            return runtime.getNil();
        }

        modify();

        if (realLength > i2) {
            realLength = i2;
            if (i2 << 1 < values.length && values.length > ARRAY_DEFAULT_SIZE) realloc(i2 << 1);
        }
        return item;
    }

    //rb_ary_delete_at
    private  function delete_at(pos:int):IRubyObject {
        var len: int =  realLength;

        if (pos >= len) return getRuntime().getNil();

        if (pos < 0) pos += len;

        if (pos < 0) return getRuntime().getNil();

        modify();

        var obj: IRubyObject =  values[pos];
        System.arraycopy(values, pos + 1, values, pos, len - (pos + 1));

        realLength--;

        return obj;
    }

    //rb_ary_delete_at_m
    public function delete_at2(obj:IRubyObject):IRubyObject {
        return super.delete_at( RubyNumeric.num2long(obj));
    }

    //rb_ary_reject_bang
    public  function reject(context:ThreadContext, block:Block):IRubyObject {
        var ary: RubyArray =  aryDup();
        ary.reject_bang(context, block);
        return ary;
    }

    //rb_ary_reject_bang
    public  function reject_bang(context:ThreadContext, block:Block):IRubyObject {
        var i2: int =  0;
        modify();

        for (var i1:int = 0; i1 < realLength; i1++) {
            var v: IRubyObject =  values[i1];
            if (block.yield(context, v).isTrue()) continue;

            if (i1 != i2) store(i2, v);
            i2++;
        }
        if (realLength == i2) return getRuntime().getNil();

        if (i2 < realLength) realLength = i2;

        return this;
    }

    //rb_ary_delete_if
    public  function delete_if(context:ThreadContext, block:Block):IRubyObject {
        reject_bang(context, block);
        return this;
    }

    // rb_ary_zip

    public  function zip(context:ThreadContext, args:Array, block:Block):IRubyObject {
        for (var i:int = 0; i < args.length; i++) {
            args[i] = args[i].convertToArray();
        }

        var runtime: Ruby =  getRuntime();
        if (block.isGiven()) {
            for (var i:int = 0; i < realLength; i++) {
                var tmp: RubyArray =  new RubyArray(runtime, args.length + 1);
                tmp.append(elt(i));
                for (var j:int = 0; j < args.length; j++) {
                    tmp.append(( args[j]).elt(i));
                }
                block.yield(context, tmp);
            }
            return runtime.getNil();
        }
        
        var len: int =  realLength;
        var result: RubyArray =  new RubyArray(runtime, len);
        for (var i:int = 0; i < len; i++) {
            var tmp: RubyArray =  new RubyArray(runtime, args.length + 1);
            tmp.append(elt(i));
            for (var j:int = 0; j < args.length; j++) {
                tmp.append(( args[j]).elt(i));
            }
            result.append(tmp);
        }
        return result;
    }

    // rb_ary_cmp
    public  function op_cmp(context:ThreadContext, obj:IRubyObject):IRubyObject {
        var ary2: RubyArray =  obj.convertToArray();

        var len: int =  realLength;

        if (len > ary2.realLength) len = ary2.realLength;

        var runtime: Ruby =  getRuntime();
        for (var i:int = 0; i < len; i++) {
            var v: IRubyObject =  elt(i).callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", ary2.elt(i));
            if (!(v instanceof RubyFixnum) || ( v).getLongValue() != 0) return v;
        }
        len = realLength - ary2.realLength;

        if (len == 0) return RubyFixnum.zero(runtime);
        if (len > 0) return RubyFixnum.one(runtime);

        return RubyFixnum.minus_one(runtime);
    }

    // rb_ary_slice_bang
    public  function slice_bang(args:Array):IRubyObject {
        if (args.length == 2) {
            var pos: Number =  RubyNumeric.num2long(args[0]);
            var len: Number =  RubyNumeric.num2long(args[1]);
            
            if (pos < 0) pos = realLength + pos;

            args[1] = subseq(pos, len);
            splice(pos, len, null);
            
            return args[1];
        }
        
        var arg: IRubyObject =  args[0];
        if (arg instanceof RubyRange) {
            var beglen: Array =  (arg).begLen(realLength, 1);
            var pos: Number =  beglen[0];
            var len: Number =  beglen[1];

            if (pos < 0) {
                pos = realLength + pos;
            }
            arg = subseq(pos, len);
            splice(pos, len, null);
            return arg;
        }

        return delete_at( RubyNumeric.num2long(args[0]));
    }

    // rb_ary_assoc
    public  function assoc(context:ThreadContext, key:IRubyObject):IRubyObject {
        var runtime: Ruby =  getRuntime();

        for (var i:int = begin; i < begin + realLength; i++) {
            var v: IRubyObject =  values[i];
            if (v instanceof RubyArray) {
                var arr: RubyArray =  RubyArray(v);
                if (arr.realLength > 0 && equalInternal(context, arr.values[arr.begin], key).isTrue()) return arr;
            }
        }

        return runtime.getNil();
    }

    // rb_ary_rassoc
    public  function rassoc(context:ThreadContext, value:IRubyObject):IRubyObject {
        var runtime: Ruby =  getRuntime();

        for ( var i:int= begin; i < begin + realLength; i++) {
            var v: IRubyObject =  values[i];
            if (v instanceof RubyArray) {
                var arr: RubyArray =  RubyArray(v);
                if (arr.realLength > 1 && equalInternal(context, arr.values[arr.begin + 1], value).isTrue()) return arr;
            }
        }

        return runtime.getNil();
    }

    // flatten
    private  function flatten(context:ThreadContext, index:int, ary2:RubyArray, memo:RubyArray):int {
        var i: int =  index;
        var n:int;
        var lim: int =  index + ary2.realLength;

        var id: IRubyObject =  ary2.id();

        if (memo.includes(context, id)) throw getRuntime().newArgumentError("tried to flatten recursive array");

        memo.append(id);
        splice(index, 1, ary2);
        while (i < lim) {
            var tmp: IRubyObject =  elt(i).checkArrayType();
            if (!tmp.isNil()) {
                n = this.flatten(context, i,  tmp, memo);
                i += n;
                lim += n;
            }
            i++;
        }
        memo.pop();
        return lim - index - 1; // returns number of increased items 
    }

    // rb_ary_flatten_bang
    public  function flatten_bang(context:ThreadContext):IRubyObject {
        var i: int =  0;
        var memo: RubyArray =  null;

        while (i < realLength) {
            var ary2: IRubyObject =  values[begin + i];
            var tmp: IRubyObject =  ary2.checkArrayType();
            if (!tmp.isNil()) {
                if (memo == null) {
                    memo = new RubyArray(getRuntime(), false);
                    memo.values = reserve(ARRAY_DEFAULT_SIZE);
                }

                i += this.flatten(context, i,  tmp, memo);
            }
            i++;
        }
        if (memo == null) return getRuntime().getNil();

        return this;
    }

    // rb_ary_flatten
    public  function flatten2(context:ThreadContext):IRubyObject {
        var ary: RubyArray =  aryDup();
        ary.flatten_bang(context);
        return ary;
    }

    // rb_ary_nitems
    public  function nitems():IRubyObject {
        var n: int =  0;

        for ( var i:int= begin; i < begin + realLength; i++) {
            if (!values[i].isNil()) n++;
        }
        
        return getRuntime().newFixnum(n);
    }

    // rb_ary_plus
    public  function op_plus(obj:IRubyObject):IRubyObject {
        var y: RubyArray =  obj.convertToArray();
        var len: int =  realLength + y.realLength;
        var z: RubyArray =  new RubyArray(getRuntime(), len);
        System.arraycopy(values, begin, z.values, 0, realLength);
        System.arraycopy(y.values, y.begin, z.values, realLength, y.realLength);
        z.realLength = len;
        return z;
    }

    // rb_ary_times
    public  function op_times(context:ThreadContext, times:IRubyObject):IRubyObject {
        var tmp: IRubyObject =  times.checkStringType();

        if (!tmp.isNil()) return join(context, tmp);

        var len: Number =  RubyNumeric.num2long(times);
        if (len == 0) return new RubyArray(getRuntime(), getMetaClass(), 0);
        if (len < 0) throw getRuntime().newArgumentError("negative argument");

        if (Number.MAX_VALUE / len < realLength) {
            throw getRuntime().newArgumentError("argument too big");
        }

        len *= realLength;

        var ary2: RubyArray =  new RubyArray(getRuntime(), getMetaClass(), len);
        ary2.realLength = len;

        for ( var i:int= 0; i < len; i += realLength) {
            System.arraycopy(values, begin, ary2.values, i, realLength);
        }

        ary2.infectBy(this);

        return ary2;
    }

    // ary_make_hash
    private  function makeHash(ary2:RubyArray):RubyHash {
        var hash: RubyHash =  new RubyHash(getRuntime(), false);
        var begin: int =  this.begin;
        for ( var i:int= begin; i < begin + realLength; i++) {
            hash.fastASet(values[i], NEVER);
        }

        if (ary2 != null) {
            begin = ary2.begin;            
            for ( var i:int= begin; i < begin + ary2.realLength; i++) {
                hash.fastASet(ary2.values[i], NEVER);
            }
        }
        return hash;
    }

    // rb_ary_uniq_bang
    public  function uniq_bang():IRubyObject {
        var hash: RubyHash =  makeHash(null);

        if (realLength == hash.size()) return getRuntime().getNil();

        var j: int =  0;
        for ( var i:int= 0; i < realLength; i++) {
            var v: IRubyObject =  elt(i);
            if (hash.fastDelete(v)) store(j++, v);
        }
        realLength = j;
        return this;
    }

    // rb_ary_uniq
    public  function uniq():IRubyObject {
        var ary: RubyArray =  aryDup();
        ary.uniq_bang();
        return ary;
    }

    // rb_ary_diff
    public  function op_diff(other:IRubyObject):IRubyObject {
        var hash: RubyHash =  other.convertToArray().makeHash(null);
        var ary3: RubyArray =  new RubyArray(getRuntime());

        var begin: int =  this.begin;
        for ( var i:int= begin; i < begin + realLength; i++) {
            if (hash.fastARef(values[i]) != null) continue;
            ary3.append(elt(i - begin));
        }

        return ary3;
    }

    // rb_ary_and
    public  function op_and(other:IRubyObject):IRubyObject {
        var ary2: RubyArray =  other.convertToArray();
        var hash: RubyHash =  ary2.makeHash(null);
        var ary3: RubyArray =  new RubyArray(getRuntime(), 
                realLength < ary2.realLength ? realLength : ary2.realLength);

        for ( var i:int= 0; i < realLength; i++) {
            var v: IRubyObject =  elt(i);
            if (hash.fastDelete(v)) ary3.append(v);
        }

        return ary3;
    }

    // rb_ary_or
    public  function op_or(other:IRubyObject):IRubyObject {
        var ary2: RubyArray =  other.convertToArray();
        var set: RubyHash =  makeHash(ary2);

        var ary3: RubyArray =  new RubyArray(getRuntime(), realLength + ary2.realLength);

        for ( var i:int= 0; i < realLength; i++) {
            var v: IRubyObject =  elt(i);
            if (set.fastDelete(v)) ary3.append(v);
        }
        for ( var i:int= 0; i < ary2.realLength; i++) {
            var v: IRubyObject =  ary2.elt(i);
            if (set.fastDelete(v)) ary3.append(v);
        }
        return ary3;
    }

    // rb_ary_sort
    public  function sort(block:Block):RubyArray {
        var ary: RubyArray =  aryDup();
        ary.sort_bang(block);
        return ary;
    }

    // rb_ary_sort_bang
    public  function sort_bang(block:Block):RubyArray {
        modify();
        if (realLength > 1) {
            flags |= TMPLOCK_ARR_F;
      
                if (block.isGiven()) {
                    Arrays.sort(values, 0, realLength, new BlockComparator(block));
                } else {
                    Arrays.sort(values, 0, realLength, new DefaultComparator());
                }
  
                flags &= ~TMPLOCK_ARR_F;
            
        }
        return this;
    }

    class BlockComparator implements Comparator {
        private  var block:Block;

        public  function BlockComparator(block:Block) {
            this.block = block;
        }

        public  function compare(o1:Object, o2:Object):int {
            var context: ThreadContext =  getRuntime().getCurrentContext();
            var obj1: IRubyObject =  (IRubyObject) o1;
            var obj2: IRubyObject =  (IRubyObject) o2;
            var ret: IRubyObject =  block.yield(context, getRuntime().newArray(obj1, obj2), null, null, true);
            var n: int =  RubyComparable.cmpint(context, ret, obj1, obj2);
            //TODO: ary_sort_check should be done here
            return n;
        }
    }

    static  class DefaultComparator implements Comparator {
        public  function compare(o1:Object, o2:Object):int {
            if (o1 instanceof RubyFixnum && o2 instanceof RubyFixnum) {
                var a: Number =  ( o1).getLongValue();
                var b: Number =  ( o2).getLongValue();
                if (a > b) return 1;
                if (a < b) return -1;
                return 0;
            }
            if (o1 instanceof RubyString && o2 instanceof RubyString) {
                return ( o1).op_cmp( o2);
            }

            var obj1: IRubyObject =  (IRubyObject) o1;
            var obj2: IRubyObject =  (IRubyObject) o2;

            var context: ThreadContext =  obj1.getRuntime().getCurrentContext();
            var ret: IRubyObject =  obj1.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", obj2);
            var n: int =  RubyComparable.cmpint(context, ret, obj1, obj2);
            //TODO: ary_sort_check should be done here
            return n;
        }
    }

    public static function marshalTo(array:RubyArray, output:MarshalStream):void {
        output.registerLinkTarget(array);
        output.writeInt(array.getList().size());
        for (var obj:Object in array.getList()) {
            output.dumpObject( obj);
        }
    }

    public static function unmarshalFrom(input:UnmarshalStream):RubyArray  {
        var result: RubyArray =  input.getRuntime().newArray();
        input.registerLinkTarget(result);
        var size: int =  input.unmarshalInt();
        for ( var i:int= 0; i < size; i++) {
            result.append(input.unmarshalObject());
        }
        return result;
    }

    // @see org.jruby.util.Pack#pack
    public  function pack(context:ThreadContext, obj:IRubyObject):RubyString {
        var iFmt: RubyString =  RubyString.objAsString(context, obj);
        return Pack.pack(getRuntime(), this, iFmt.getByteList());
    }

    public override function getJavaClass():Class {
        return List;
    } 
*/
    // Satisfy java.util.List interface (for Java integration)
    public  function size():int {
        return realLength;
    }

    public  function isEmpty():Boolean {
        return realLength == 0;
    }

    public  function contains(element:Object):Boolean {
        return indexOf(element) != -1;
    }

    public  function toArray():Array {
        var array: Array =  new Object[realLength];
        for ( var i:int= begin; i < realLength; i++) {
            array[i - begin] = AsUtil.convertRubyToJava(values[i]);
        }
        return array;
    }
/*
    public  function toArray2(arg:Array):Array {
        var array: Array =  arg;
        if (array.length < realLength) {
            var type: Class =  array.getClass().getComponentType();
            array =  Array.newInstance(type, realLength);
        }
        var length: int =  realLength - begin;

        for ( var i:int= 0; i < length; i++) {
            array[i] = JavaUtil.convertRubyToJava(values[i + begin]);
        }
        return array;
    }
*/
    public  function add(element:Object):Boolean {
        //append(JavaUtil.convertJavaToRuby(getRuntime(), element));
        return true;
    }
/*
    public  function remove(element:Object):Boolean {
        var deleted: IRubyObject =  delete(getRuntime().getCurrentContext(), JavaUtil.convertJavaToRuby(getRuntime(), element), Block.NULL_BLOCK);
        return deleted.isNil() ? false : true; // TODO: is this correct ?
    }

    public  function containsAll(c:Array):Boolean {
        for (var obj:Object in c.iterator()) {
            if (indexOf(iter.next()) == -1) {
                return false;
            }
        }

        return true;
    }

    public  function addAll(c:Array):Boolean {
        for (var obj:Object in c.iterator()) {
            add(iter.next());
        }
        return !c.isEmpty();
    }

    public  function addAll2(index:int, c:Array):Boolean {
        var iter: Iterator =  c.iterator();
        for ( var i:int= index; iter.hasNext(); i++) {
            add(i, iter.next());
        }
        return !c.isEmpty();
    }

    public  function removeAll(c:Array):Boolean {
        var listChanged: Boolean =  false;
        for (var obj:Object in c.iterator()) {
            if (remove(iter.next())) {
                listChanged = true;
            }
        }
        return listChanged;
    }

    public  function retainAll(c:Array):Boolean {
        var listChanged: Boolean =  false;

        for (var obj:Object in c.iterator()) {
            var element: Object =  iter.next();
            if (!c.contains(element)) {
                remove(element);
                listChanged = true;
            }
        }
        return listChanged;
    }

    public  function getObject(index:int):Object {
        return JavaUtil.convertRubyToJava( elt(index), Object);
    }

    public  function setObject(index:int, element:Object):Object {
        return store(index, JavaUtil.convertJavaToRuby(getRuntime(), element));
    }

    // TODO: make more efficient by not creating IRubyArray[]
    public  function add2(index:int, element:Object):void {
        insert([RubyFixnum.newFixnum(getRuntime(), index), JavaUtil.convertJavaToRuby(getRuntime(), element)]);
    }

    public  function remove2(index:int):Object {
        return JavaUtil.convertRubyToJava(this.delete_at(index), Object);
    }
    */
    public  function indexOf(element:Object):int {
        var begin: int =  this.begin;

        if (element == null) {
            for ( var i:int= begin; i < begin + realLength; i++) {
                if (values[i] == null) {
                    return i;
                }
            }
        } else {
            var convertedElement: IRubyObject =  AsUtil.convertJavaToRuby(getRuntime(), element);

            for ( var i:int= begin; i < begin + realLength; i++) {
                if (convertedElement == values[i]) {
                    return i;
                }
            }
        }
        return -1;
    }

    public  function lastIndexOf(element:Object):int {
        var begin: int =  this.begin;

        if (element == null) {
            for ( var i:int= begin + realLength - 1; i >= begin; i--) {
                if (values[i] == null) {
                    return i;
                }
            }
        } else {
            var convertedElement: IRubyObject =  AsUtil.convertJavaToRuby(getRuntime(), element);

            for ( var i:int= begin + realLength - 1; i >= begin; i--) {
                if (convertedElement == values[i]) {
                    return i;
                }
            }
        }

        return -1;
    }
/*
    public class RubyArrayConversionIterator implements Iterator {
        protected  var index: int =  0;
        protected  var last: int =  -1;

        public  function hasNext():Boolean {
            return index < realLength;
        }

        public  function next():Object {
            var element: IRubyObject =  elt(index);
            last = index++;
            return JavaUtil.convertRubyToJava(element, Object);
        }

        public  function remove():void {
            if (last == -1) throw new IllegalStateException();

            delete_at(last);
            if (last < index) index--;

            last = -1;
	
        }
    }

    public  function iterator():Iterator {
        return new RubyArrayConversionIterator();
    }
    
        class RubyArrayConversionListIterator extends RubyArrayConversionIterator implements ListIterator {
        public  function RubyArrayConversionListIterator() {
        }

        public  function RubyArrayConversionListIterator(index:int) {
            this.index = index;
		}

		public  function hasPrevious():Boolean {
            return index >= 0;
		}

		public  function previous():Object {
            return JavaUtil.convertRubyToJava( elt(last = --index), Object);
		}

		public  function nextIndex():int {
            return index;
		}

		public  function previousIndex():int {
            return index - 1;
		}

        public  function set(obj:Object):void {
            if (last == -1) throw new IllegalStateException();

            store(last, JavaUtil.convertJavaToRuby(getRuntime(), obj));
        }

        public  function add(obj:Object):void {
            insert(new IRubyObject[] { RubyFixnum.newFixnum(getRuntime(), index++), JavaUtil.convertJavaToRuby(getRuntime(), obj) });
            last = -1;
		}
    } 

    public  function listIterator():ListIterator {
        return new RubyArrayConversionListIterator();
    }

    public  function listIterator2(index:int):ListIterator {
        return new RubyArrayConversionListIterator(index);
	}
	

    // TODO: list.subList(from, to).clear() is supposed to clear the sublist from the list.
    // How can we support this operation?
    public  function subList(fromIndex:int, toIndex:int):Array {
        if (fromIndex < 0 || toIndex > size() || fromIndex > toIndex) {
            throw new IndexOutOfBoundsException();
        }
        
        var subList: IRubyObject =  subseq(fromIndex, toIndex - fromIndex + 1);

        return subList.isNil() ? null : subList;
    }

    public  function clear():void {
        rb_clear();
    }
    */
}
}